﻿/*----------------------------------------------------------------
// Copyright (C) 2023 版权所有。 
//
// 文件功能描述：   Stacking控制类，由任务调度者直接使用
// 文件创建者：     王志国
// 时间：           2023 年 7 月 12 日
// 
// 修改履历：
// 时间         作者       修改内容                   修改原因
// 2023/7/12   王志国      新建
//----------------------------------------------------------------*/
using Goway.Log4net;
using Goway.StackingHelper.Inovance;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Goway.Stacking.StackingControl
{
    /// <summary>
    /// Stacking 控制
    /// </summary>
    public class StackingController
    {
        #region 基本属性相关

        /// <summary>
        /// 所属库区信息
        /// </summary>
        private WarehouseArea warehouseArea;

        /// <summary>
        /// 当前堆垛机信息
        /// </summary>
        private DeviceInfo stackingInfo;

        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool IsConnected { get; private set; }

        /// <summary>
        /// 每包物料数/每次整取整放移动的数
        /// </summary>
        public ushort MaterialCountPeerPackage { get; private set; }

        /// <summary>
        /// 库排数
        /// </summary>
        private ushort storageRowCount;

        /// <summary>
        /// 库列数
        /// </summary>
        private ushort storeColumnCount;

        /// <summary>
        /// 库层数
        /// </summary>
        private ushort storageLayerCount;

        /// <summary>
        /// 库临时区域层数
        /// </summary>
        private ushort storageTempAreaLayerCount;

        #endregion

        #region 任务相关

        /// <summary>
        /// 入库任务队列
        /// </summary>
        public StackingTaskQueue InputTaskQueue { get; private set; }

        /// <summary>
        /// 出库/移库任务队列
        /// </summary>
        public StackingTaskQueue OutputTaskQueue { get; private set; }

        /// <summary>
        /// 当前任务信息
        /// </summary>
        private StackingTaskInfo currentTaskInfo;

        /// <summary>
        /// Stacking 通知消息
        /// </summary>
        private StackingNotify notifyObj;

        #endregion

        #region 物料坐标

        /// <summary>
        /// 取料坐标
        /// </summary>
        private ushort[,] fromCoordinate;

        /// <summary>
        /// 放料坐标
        /// </summary>
        private ushort[,] toCoordinate;

        /// <summary>
        /// 入库缓存坐标
        /// </summary>
        public static ushort[,] InputBufferCoord { get; private set; }

        /// <summary>
        /// 出库缓存坐标
        /// </summary>
        public static ushort[,] OutputBufferCoord { get; private set; }

        /// <summary>
        /// 放料位置故障
        /// </summary>
        public short[] MalfunctionToCoordinate { get; private set; }

        /// <summary>
        /// 入库缓存物料数量（其它模块传入）
        /// </summary>
        private int inputBufMaterialCount;

        /// <summary>
        /// 是否修改入库缓存的物料数量
        /// </summary>
        private bool isModifiedInputBuf;

        #endregion

        #region 出入库缓存坐标相关配置信息

        /// <summary>
        /// 入库缓存所在列号
        /// </summary>
        private ushort inputAtColumnIdx;

        /// <summary>
        /// 入库缓存起始层号
        /// </summary>
        private ushort inputBeginLayerIdx;

        /// <summary>
        /// 入库缓存终止层号
        /// </summary>
        private ushort inputEndLayerIdx;

        /// <summary>
        /// 出库缓存所在列号
        /// </summary>
        private ushort outputAtColumnIdx;

        /// <summary>
        /// 出库缓存起始层号
        /// </summary>
        private ushort outputBeginLayerIdx;

        /// <summary>
        /// 出库缓存终止层号
        /// </summary>
        private ushort outputEndLayerIdx;

        #endregion

        #region 常量定义

        /// <summary>
        /// 坐标数组行数
        /// </summary>
        public const int COORDINATE_ROW_COUNT = 8;

        /// <summary>
        /// 坐标数组列数
        /// </summary>
        public const int COORDINATE_COLUMN_COUNT = 3;

        /// <summary>
        /// 读取控制位位数
        /// </summary>
        private const int READ_CONTROL_BIT_COUNT = 11;

        /// <summary>
        /// Stacking 默认端口
        /// </summary>
        private const int STACKING_DEFAULT_PORT = 502;

        /// <summary>
        /// 多次访问Stacking时间间隔
        /// </summary>
        private const int WAIT_TIME_INTEVAL = 200;

        #endregion

        #region 增加到模块管理中的委托和事件

        /// <summary>
        /// 消息显示委托
        /// </summary>
        /// <param name="message">消息</param>
        public delegate void ShowMessage(string message);

        /// <summary>
        /// 消息显示事件
        /// </summary>
        public event ShowMessage ShowWndMessage;

        #endregion

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="area">库区信息</param>
        /// <param name="observers">观察者对象</param>
        /// <param name="materialCountPeerPackage">每包物料数</param>
        /// <param name="tempAreaLayerCount">临时区域层数</param>
        /// <param name="inputAtColumnIdx">入库缓存所在列</param>
        /// <param name="inputBeginLayerIdx">入库缓存起始层号</param>
        /// <param name="inputEndLayerIdx">入库缓存终止层号</param>
        /// <param name="outputAtColumnIdx">出库缓存所在列</param>
        /// <param name="outputBeginLayerIdx">出库缓存起始层号</param>
        /// <param name="outputEndLayerIdx">出库缓存终止层号</param>
        /// <param name="errorMsg">错误消息</param>
        /// <remarks>所有坐标都从1开始，不能为 0</remarks>
        /// <returns>是否正常完成</returns>
        public bool Initialization(WarehouseArea area, List<Observer> observers, ushort materialCountPeerPackage,
            ushort tempAreaLayerCount, ushort inputAtColumnIdx, ushort inputBeginLayerIdx,
            ushort inputEndLayerIdx, ushort outputAtColumnIdx, ushort outputBeginLayerIdx,
            ushort outputEndLayerIdx, out string errorMsg)
        {
            errorMsg = "";

            if (area == null || 0 == materialCountPeerPackage || 0 == tempAreaLayerCount ||
                0 == inputAtColumnIdx || 0 == inputBeginLayerIdx || 0 == inputEndLayerIdx ||
                0 == outputAtColumnIdx || 0 == outputBeginLayerIdx || 0 == outputEndLayerIdx)
            {
                errorMsg = "参数错误，初始化失败！";
                return false;
            }

            if (area == null)
            {
                errorMsg = "当前 Stacking 所在库区信息为空，初始化失败！";
                return false;
            }

            this.warehouseArea = area;
            this.observers = observers;

            // 初始化各参数
            this.storageRowCount = warehouseArea.RowCount.HasValue ? warehouseArea.RowCount.Value : (ushort)0;
            this.storeColumnCount = warehouseArea.ColumnCount.HasValue ? warehouseArea.ColumnCount.Value : (ushort)0;
            this.storageLayerCount = warehouseArea.LayerCount.HasValue ? warehouseArea.LayerCount.Value : (ushort)0;

            this.MaterialCountPeerPackage = materialCountPeerPackage;
            this.storageTempAreaLayerCount = tempAreaLayerCount;

            this.inputAtColumnIdx = inputAtColumnIdx;
            this.inputBeginLayerIdx = inputBeginLayerIdx;
            this.inputEndLayerIdx = inputEndLayerIdx;
            this.outputAtColumnIdx = outputAtColumnIdx;
            this.outputBeginLayerIdx = outputBeginLayerIdx;
            this.outputEndLayerIdx = outputEndLayerIdx;

            if (!InitCoordinate(out errorMsg))
            {
                errorMsg = $"Stacking出入库缓存坐标初始化错误：{errorMsg}，初始化失败！";
                return false;
            }

            DeviceInfoService serv = new DeviceInfoService();
            this.stackingInfo = serv.GetSingle(stacking => stacking.AreaId == this.warehouseArea.Id &&
                stacking.DeviceType == (int)DeviceType.Stacking);
            if (this.stackingInfo == null)
            {
                errorMsg = "未能在数据库查询到该库区的Stacking信息，初始化失败！";
                return false;
            }

            // 任务队列初始化
            this.InputTaskQueue = new StackingTaskQueue();
            this.OutputTaskQueue = new StackingTaskQueue();

            return true; // this.Connect(out errorMsg);
        }

        /// <summary>
        /// 设置入库信息的物料数量
        /// </summary>
        /// <param name="notifyContent"></param>
        public void SetInputBufMaterialCount(int inputBufMaterialCount)
        {
            this.inputBufMaterialCount = inputBufMaterialCount;
            isModifiedInputBuf = true;
        }

        /// <summary>
        /// 初始化坐标数据
        /// </summary>
        private bool InitCoordinate(out string errorMsg)
        {
            errorMsg = "";

            this.fromCoordinate = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];
            this.toCoordinate = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];

            InputBufferCoord = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];
            OutputBufferCoord = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];

            // 初始化为 0
            for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
            {
                for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                {
                    this.fromCoordinate[i, j] = 0;
                    this.toCoordinate[i, j] = 0;

                    InputBufferCoord[i, j] = 0;
                    OutputBufferCoord[i, j] = 0;
                }
            }

            // 初始化出入库缓存地址
            if (!this.warehouseArea.InputBufAtRow.HasValue || !this.warehouseArea.OutputBufAtRow.HasValue ||
                this.inputAtColumnIdx == 0 || this.inputBeginLayerIdx == 0 || this.inputEndLayerIdx == 0 ||
                this.outputAtColumnIdx == 0 || this.outputBeginLayerIdx == 0 || this.outputEndLayerIdx == 0 ||
                this.inputEndLayerIdx < this.inputBeginLayerIdx || this.outputEndLayerIdx < this.outputBeginLayerIdx ||
                this.inputEndLayerIdx - this.inputBeginLayerIdx + 1 > this.MaterialCountPeerPackage ||
                this.outputEndLayerIdx - this.outputBeginLayerIdx + 1 > this.MaterialCountPeerPackage)
            {
                errorMsg = $"库区{this.warehouseArea.Id}出入库坐标的系统参数错误，或坐标数据非法。";
                return false;
            }

            for (int i = 0; i < this.inputEndLayerIdx - this.inputBeginLayerIdx + 1; i++)
            {
                InputBufferCoord[i, 0] = this.warehouseArea.InputBufAtRow.Value;
                InputBufferCoord[i, 1] = this.inputAtColumnIdx;
                InputBufferCoord[i, 2] = (ushort)(this.inputBeginLayerIdx + i);
            }

            for (int i = 0; i < this.outputEndLayerIdx - this.outputBeginLayerIdx + 1; i++)
            {
                OutputBufferCoord[i, 0] = this.warehouseArea.OutputBufAtRow.Value;
                OutputBufferCoord[i, 1] = this.outputAtColumnIdx;
                OutputBufferCoord[i, 2] = (ushort)(this.outputBeginLayerIdx + i);
            }

            return true;
        }

        /// <summary>
        /// Stacking 控制线程处理方法
        /// </summary>
        /// <param name="token"></param>
        public void StackingDoWork(object token)
        {
            string errorMsg = "";                              // 错误消息

            if (token is CancellationToken)
            {
                var tok_source = (CancellationToken)token;
                while (!tok_source.IsCancellationRequested)
                {
                    try
                    {
                        Thread.Sleep(WAIT_TIME_INTEVAL);

                        // 判断堆垛机是否连接
                        if (!CheckIsConnect(out errorMsg))
                        {
                            LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]连接错误，{errorMsg}");
                            continue;
                        }

                        // 查看所有状态
                        short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RUN_STATUS,
                            READ_CONTROL_BIT_COUNT, out errorMsg);
                        if (readResult != null && readResult.Length == READ_CONTROL_BIT_COUNT)
                        {
                            // 查看所有标志位 <测试，暂时屏蔽>
                            // <1> 自动运行状态 ProtocolAddress.R_RUN_STATUS 为参照位
                            //if (readResult[ProtocolAddress.R_RUN_STATUS - ProtocolAddress.R_RUN_STATUS] ==
                            //    RWContent.RW_VALUE_0)
                            //{
                            //    continue;
                            //}

                            // <2> 请求扫码状态
                            if (readResult[ProtocolAddress.R_Stacking_REQUEST_SCAN_BARCODE -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                // 扫码通知
                                NotifyScanStatus();
                                RequestScanStatusProcess();
                            }

                            // <3> 忙碌状态  <测试，暂时屏蔽>
                            //if (readResult[ProtocolAddress.R_BUSY_STATUS - ProtocolAddress.R_RUN_STATUS] ==
                            //    RWContent.RW_VALUE_1)
                            //{
                            //    RequestScanStatusProcess();                  // 忙碌状态下可进料到入库缓存 
                            //    continue;
                            //}

                            // <4> PLC 收到扫码完成
                            if (readResult[ProtocolAddress.R_RECV_SCAN_FINISHED_FLAG -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                RecvScanFinishedProcess();
                            }

                            // <5> PLC 入库缓存完成
                            if (readResult[ProtocolAddress.R_INPUT_BUFFER_FINISHED -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                InputBufFinishedProcess();
                            }

                            // <6> 入库缓存取料完成
                            if (readResult[ProtocolAddress.R_INPUT_BUFFER_TAKE_FINISHED -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                // 入库完成，物料继续进入入库缓存
                                InputBufTakeFinishedProcess();
                            }

                            // <7> 入库缓存个数
                            if (readResult[ProtocolAddress.R_INPUT_BUFFER_COUNT -
                                ProtocolAddress.R_RUN_STATUS] == this.MaterialCountPeerPackage)
                            {
                                // 缓存满，通知入库
                                InputBufFullProcess();
                            }

                            // <8> 入库缓存状态 0：空闲；1：忙碌中
                            if (readResult[ProtocolAddress.R_INPUT_BUFFER_STATUS -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_0)
                            {
                                // 通知可以进料到入库缓存
                                InputBufStatusProcess();
                            }

                            // <9> 出库缓存状态 0：空闲；1：忙碌中
                            if (readResult[ProtocolAddress.R_OUTPUT_BUFFER_STATUS -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_0)
                            {
                                // 出库处理
                                OutputBufStatusProcess();
                            }

                            // <10> 收到任务
                            if (readResult[ProtocolAddress.R_RECEIVED_TASK_FLAG -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                // 收到任务流程处理
                                if (!RecvTaskProcess())
                                {
                                    LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]收到任务处理失败。");
                                }
                            }

                            // <11> 任务完成
                            if (readResult[ProtocolAddress.R_TASK_FINISHED_FLAG -
                                ProtocolAddress.R_RUN_STATUS] == RWContent.RW_VALUE_1)
                            {
                                ResponseTaskFinished();
                                NotifyObserverTaskFinished();
                            }
                            else
                            {
                                ResetRecvTaskFinished();
                            }
                        }
                        else
                        {
                            LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]读取状态位失败。 {errorMsg}");
                            continue;
                        }
                    }
                    catch (Exception ex)
                    {
                        LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]发生异常：{ex.Message}");
                    }
                }

                // 断开连接
                if (!this.DisConnect(out errorMsg))
                {
                    LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]断开连接失败 {errorMsg}");
                    return;
                }
            }
        }

        #region <2> 请求扫码状态处理

        /// <summary>
        /// 通知观察者
        /// </summary>
        /// <returns></returns>
        private bool NotifyScanStatus()
        {
            // 通知观察者扫码
            this.notifyObj = new StackingNotify()
            {
                CurrentTaskNo = 0,
                NotifyType = StackingNotifyType.ScanRequest
            };

            notifyObserver();
            return true;
        }

        /// <summary>
        /// 请求扫码状态处理
        /// </summary>
        /// <returns></returns>
        private bool RequestScanStatusProcess()
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            TimeSpan runTime = TimeSpan.Zero;

            // 开启计时
            sw.Start();

            if (!isModifiedInputBuf)
            {
                return false;
            }

            isModifiedInputBuf = false;

            short[] inputCoord = GetInputCoordBE(this.inputBufMaterialCount);
            if (inputCoord == null)
            {
                LoggerHelper.Error($"");
                return false;
            }            

            // 写入库信息 1300
            if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_INUPT_BUFFER_ADDR, 6,
                    inputCoord, out string errorMsg))
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]入库信息写入失败！{errorMsg}");
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                return false;
            }

            // 扫码完成标志置1   1002
            if (RWErrorCode.SUCCESS != WriteStackingInt16Data(
                    ProtocolAddress.W_SCAN_FINISHED, 1,
                    new short[] { RWContent.RW_VALUE_1 }, out errorMsg))
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]写入扫码完成标志失败！{errorMsg}");
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                return false;
            }

            sw.Stop();
            runTime = sw.Elapsed;
            LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]写入扫码完成标志，耗时{runTime.TotalMilliseconds}ms");
            return true;
        }

        #endregion

        #region <4> PLC 收到扫码完成处理

        /// <summary>
        /// PLC 收到扫码完成处理
        /// </summary>
        /// <returns></returns>
        private bool RecvScanFinishedProcess()
        {
            // MES 扫码完成标志复位
            int errorCode = WriteStackingInt16Data(ProtocolAddress.W_SCAN_FINISHED, 1,
                    new short[] { RWContent.RW_VALUE_0 }, out string errorMsg);
            if (RWErrorCode.SUCCESS != errorCode)
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]复位扫码完成标志写入失败！{errorMsg}");

                // 错误处理

                return false;
            }

            return true;
        }

        /// <summary>
        /// 获得入库缓存的起止坐标
        /// </summary>
        /// <param name="inputBufMaterialCount"></param>
        /// <returns></returns>
        private short[] GetInputCoordBE(int inputBufMaterialCount)
        {
            if(inputBufMaterialCount <= 0 || inputBufMaterialCount > MaterialCountPeerPackage)
            {
                return null;
            }

            short[] coord = new short[ProtocolAddress.INPUT_INFO_LENGTH];
            coord[0] = (short)InputBufferCoord[MaterialCountPeerPackage - inputBufMaterialCount, 0];
            coord[1] = (short)InputBufferCoord[MaterialCountPeerPackage - inputBufMaterialCount, 1];
            coord[2] = (short)InputBufferCoord[MaterialCountPeerPackage - inputBufMaterialCount, 2];
            coord[3] = (short)InputBufferCoord[MaterialCountPeerPackage - 1, 0];
            coord[4] = (short)InputBufferCoord[MaterialCountPeerPackage - 1, 1];
            coord[5] = (short)InputBufferCoord[MaterialCountPeerPackage - 1, 2];
            return coord;
        }

        #endregion

        #region <5> PLC 入库缓存完成处理

        /// <summary>
        /// PLC 入库缓存完成处理
        /// </summary>
        /// <returns></returns>
        private bool InputBufFinishedProcess()
        {
            StackingTaskInfo taskInfo = null;

            if (this.currentTaskInfo != null && this.currentTaskInfo.TaskStatus != StackingTaskStatus.UnKnown)
            {
                // 任务执行中，返回
                return true;
            }

            // 发送入库指令
            if (this.InputTaskQueue.TaskQueue.Count > 0)
            {
                if (!this.InputTaskQueue.TaskQueue.TryPeek(out taskInfo))
                {
                    return false;
                }

                this.currentTaskInfo = taskInfo;

                // 判断任务信息合法性
                if (this.currentTaskInfo.SpaceMaterialInfo == null ||
                    this.currentTaskInfo.SpaceMaterialInfo.Count == 0)
                {
                    StackingTaskInfo failedTask = null;

                    // 任务出队列，进行错误处理
                    if (this.currentTaskInfo.TaskBizType == BizType.InputStorage)
                    {
                        if (this.InputTaskQueue.TaskQueue.TryDequeue(out failedTask))
                        {
                            this.notifyWarningObserver();                    // 临时，稍后处理
                        }
                    }
                    else
                    {
                        if (this.OutputTaskQueue.TaskQueue.TryDequeue(out failedTask))
                        {
                            this.notifyWarningObserver();                    // 临时，稍后处理
                        }
                    }

                    this.currentTaskInfo.TaskStatus = StackingTaskStatus.SendingOrder;
                    this.currentTaskInfo.TaskResultCode = WorkErrorCode.BizFailed;

                    string errorMessage = $"堆垛机[{this.stackingInfo.DeviceIP}]当前任务信息的取放料信息为空。";
                    LoggerHelper.Error(errorMessage);
                    this.currentTaskInfo.ErrorMsg = errorMessage;

                    return false;
                }

                // 发送指令
                if(SendTaskOrder(out string errorMsg) != WorkErrorCode.Success)
                {
                    return false;
                }
            }
            else
            {
                this.notifyObj = new StackingNotify()
                {
                    CurrentTaskNo = 0,
                    CurrentTaskStatus = StackingTaskStatus.Finished,
                    TaskBizType = BizType.InputStorage,
                    ErrorCode = WorkErrorCode.Success,
                    NotifyType = StackingNotifyType.RunTask,
                    SpaceMaterials = null, // taskInfo.SpaceMaterialInfo,
                    ErrorMessage = ""
                };

                // 通知调度者
                this.notifyObserver();
            }

            return true;
        }

        #endregion

        #region <6> 入库缓存取料完成处理

        /// <summary>
        /// 入库缓存取料完成处理
        /// </summary>
        /// <returns></returns>
        private bool InputBufTakeFinishedProcess()
        {
            // 通知调度者开始进料到入库缓存
            this.notifyObj = new StackingNotify()
            {
                NotifyType = StackingNotifyType.InputBufEmpty,
                TaskBizType = BizType.InputStorage
            };

            this.notifyObserver();
            return true;
        }

        #endregion

        #region <7> 入库缓存个数处理

        /// <summary>
        /// 入库缓存个数处理（数量已满时处理）
        /// </summary>
        /// <returns></returns>
        private bool InputBufFullProcess()
        {
            // 通知调度者开始进料到入库缓存
            this.notifyObj = new StackingNotify()
            {
                NotifyType = StackingNotifyType.InputBufFull,
                TaskBizType = BizType.InputStorage
            };

            this.notifyObserver();
            return true;
        }

        #endregion

        #region <8> 入库缓存状态 （0：空闲；1：忙碌中）

        /// <summary>
        /// 入库缓存状态处理
        /// </summary>
        /// <returns></returns>
        private bool InputBufStatusProcess()
        {
            return true;
        }

        #endregion

        #region <9> 出库缓存状态处理 （0：空闲；1：忙碌中）

        /// <summary>
        /// 出库缓存状态处理
        /// </summary>
        /// <returns></returns>
        private bool OutputBufStatusProcess()
        {
            // 发送出库指令（此处应判断入库状况，再决定是否出库，稍后提供算法）
            if (this.OutputTaskQueue.TaskQueue.Count > 0)
            {
                if (!this.OutputTaskQueue.TaskQueue.TryPeek(out StackingTaskInfo taskInfo))
                {
                    return false;
                }

                this.currentTaskInfo = taskInfo;
                SendTaskOrder(out string errorMsg);
            }

            return true;
        }

        #endregion

        #region <10> 收到任务处理

        /// <summary>
        /// 收到任务处理
        /// </summary>
        /// <returns></returns>
        private bool RecvTaskProcess()
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            string errorMsg = "";
            bool isOccurFault = false;

            TimeSpan runTime = TimeSpan.Zero;
            sw.Start();

            try
            {
                // 指令下发位，置零
                int writeResult = WriteStackingInt16Data(ProtocolAddress.W_ORDER, 1,
                    new short[] { RWContent.RW_VALUE_0 }, out errorMsg);
                if (writeResult != RWErrorCode.SUCCESS)
                {
                    LoggerHelper.Info($"堆垛机[{this.stackingInfo.DeviceIP}]写读取接收到指令位错误：{errorMsg}");
                    return false;
                }

                // 判断放料位置是否故障
                this.MalfunctionToCoordinate = ReadStackingInt16Data(ProtocolAddress.R_TO_PLACE_MALFUNCTION,
                    ProtocolAddress.MALFUNCTION_TOCOORD_LENGTH, out errorMsg);
                if (this.MalfunctionToCoordinate != null && this.MalfunctionToCoordinate.Length > 0)
                {
                    LoggerHelper.Info($"堆垛机[{this.stackingInfo.DeviceIP}]读取放料位置是否故障正确：{errorMsg}");
                    foreach (short resultValue in this.MalfunctionToCoordinate)
                    {
                        if (resultValue == RWContent.OCCUR_MALFUNCTION_2)                   // 故障
                        {
                            isOccurFault = true;
                            break;
                        }
                    }
                }
                else
                {
                    LoggerHelper.Info($"堆垛机[{this.stackingInfo.DeviceIP}]读放料位置故障错误：{errorMsg}");
                    return false;
                }

                sw.Stop();
                runTime = sw.Elapsed;
                LoggerHelper.Info($"堆垛机[{this.stackingInfo.DeviceIP}]任务已完成！耗时：{runTime.TotalMilliseconds}ms");

                // 判断是否发生异常
                if (isOccurFault)
                {
                    LoggerHelper.Info($"堆垛机[{this.stackingInfo.DeviceIP}]放料位置库位故障。");
                    return false;
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]读写异常{ex.Message}。");

                this.currentTaskInfo.TaskResultCode = WorkErrorCode.RWException;
                return false;
            }

            // 正常完成
            this.currentTaskInfo.TaskResultCode = WorkErrorCode.Success;
            return true;
        }

        #endregion

        #region <11> 任务完成处理

        /// <summary>
        /// 任务完成响应
        /// </summary>
        /// <returns></returns>
        private bool ResponseTaskFinished()
        {
            // 收到任务完成标记置 1
            if (RWErrorCode.SUCCESS != WriteStackingInt16Data(
                ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1,
                new short[] { RWContent.RW_VALUE_1 }, out string errorMsg))
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]写入收到任务完成标记失败！{errorMsg}");
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                return false;
            }

            return true;
        }

        /// <summary>
        /// 通知调度器，PLC 任务完成
        /// </summary>
        private bool NotifyObserverTaskFinished()
        {
            StackingTaskInfo taskInfo = this.currentTaskInfo;
            StackingNotifyType notifyType = StackingNotifyType.UnKnown;

            if (taskInfo == null)
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]任务信息为空");
                return false;
            }

            // 任务完成流程处理，通知调度器，进行后续流程处理
            if (taskInfo.TaskBizType == BizType.InputStorage)
            {
                if (InputTaskQueue.TaskQueue.Count == 0 || !InputTaskQueue.TaskQueue.TryDequeue(out taskInfo))
                {
                    LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]任务信息获取失败");
                    return false;
                }

                notifyType = StackingNotifyType.InputTaskFinished;
            }
            else
            {
                if (OutputTaskQueue.TaskQueue == null || OutputTaskQueue.TaskQueue.Count == 0 ||
                    !OutputTaskQueue.TaskQueue.TryDequeue(out taskInfo))
                {
                    LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]获取任务信息失败。");
                    return false;
                }

                if (taskInfo.TaskBizType == BizType.OutputStorage)
                {
                    notifyType = StackingNotifyType.OutputTaskFinished;
                }
                else
                {
                    notifyType = StackingNotifyType.ShiftingTaskFinished;
                }

            }

            // 发送通知消息
            this.notifyObj = new StackingNotify()
            {
                TaskBizType = taskInfo.TaskBizType,
                CurrentTaskNo = taskInfo.TaskNo,
                CurrentTaskStatus = taskInfo.TaskStatus,
                ErrorCode = taskInfo.TaskResultCode,
                ErrorMessage = taskInfo.ErrorMsg,
                NotifyType = notifyType,
                DataLength = taskInfo.DataLength,
                ErrorData = taskInfo.TaskErrorData,
                SpaceMaterials = taskInfo.SpaceMaterialInfo
            };

            this.notifyObserver();

            // 任务队列出列
            if(currentTaskInfo != null)
            {
                if(this.currentTaskInfo.TaskBizType == BizType.InputStorage)
                {
                    this.InputTaskQueue.TaskQueue.TryDequeue(out taskInfo);
                }
                else
                {
                    this.OutputTaskQueue.TaskQueue.TryDequeue(out taskInfo);
                }

                this.currentTaskInfo = null;
            }

            return true;
        }

        /// <summary>
        /// 重置收到任务完成标志
        /// </summary>
        /// <returns>是否正常完成</returns>
        private bool ResetRecvTaskFinished()
        {
            // 读收到任务完成标记
            short[] readResult = ReadStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1, out string errorMsg);
            if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
            {
                // 堆垛机收到完成任务标记置 0
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1,
                    new short[] { RWContent.RW_VALUE_0 }, out errorMsg))
                {
                    LoggerHelper.Error($"堆垛机完成标记置零失败！{errorMsg}");
                    return false;
                }
            }

            return true;
        }

        #endregion

        #region 连接与断开

        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool Connect(out string errorMsg)
        {
            errorMsg = "";
            string stackingIPAddr = this.stackingInfo.DeviceIP;
            int stackingPort = this.stackingInfo.DevicePort == null ? STACKING_DEFAULT_PORT : this.stackingInfo.DevicePort.Value;

            try
            {
                if (HC_AM600_ModbusTcp.Init_ETH_String(stackingIPAddr, 0,
                    stackingPort == 0 ? STACKING_DEFAULT_PORT : stackingPort))
                {
                    errorMsg = $"堆垛机已连接。";
                    IsConnected = true;

                    // 发送心跳
                    // WriteHeartbeat();
                }
                else
                {
                    errorMsg = $"堆垛机连接失败。";
                    IsConnected = false;

                    return false;
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]堆垛机连接异常：{ex.Message}");
                return false;
            }

            return true;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool DisConnect(out string errorMsg)
        {
            errorMsg = "";
            if (HC_MODBUSTCPV2.Exit_ETH())
            {
                errorMsg = "堆垛机已断开连接";

                IsConnected = false;
                return true;
            }
            else
            {
                errorMsg = "堆垛机断开连接失败";
                return false;
            }
        }

        /// <summary>
        /// 检查是否已连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        private bool CheckIsConnect(out string errorMsg)
        {
            errorMsg = "";
            if (!IsConnected)
            {
                if (!this.Connect(out errorMsg))
                {
                    errorMsg = "连接失败";
                    return false;
                }
            }

            return true;
        }

        #endregion

        #region 业务流程控制

        /// <summary>
        /// 发送心跳线程方法
        /// </summary>
        /// <param name="token"></param>
        public void SendHeartbeat(object token)
        {
            if (token is CancellationToken)
            {
                var tok_source = (CancellationToken)token;
                short writeValue = RWContent.RW_VALUE_1;
                while (!tok_source.IsCancellationRequested)
                {
                    try
                    {
                        Thread.Sleep(WAIT_TIME_INTEVAL);

                        // 写心跳
                        int writeResult = WriteStackingInt16Data(ProtocolAddress.W_HEARTBEAT, 1,
                            new short[] { writeValue }, out string errorMsg);
                        if (writeResult != RWErrorCode.SUCCESS)
                        {
                            LoggerHelper.Info($"写心跳失败：{errorMsg}");
                        }

                        // 修改心跳值
                        if (writeValue == RWContent.RW_VALUE_1)
                        {
                            writeValue = RWContent.RW_VALUE_0;
                        }
                        else
                        {
                            writeValue = RWContent.RW_VALUE_1;
                        }
                    }
                    catch (Exception ex)
                    {
                        LoggerHelper.Error($"写心跳异常：{ex}");

                        // 重连
                        if (!DisConnect(out string errorMsg))
                        {
                            LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]断开连接失败，{errorMsg}");
                        }

                        IsConnected = false;
                        if (!Connect(out errorMsg))
                        {
                            LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]连接失败，{errorMsg}");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// W_写入任务下发标志
        /// </summary>
        /// <param name="bizType">业务类型，该字段用于业务判断，不下发给 Stacking</param>
        /// <param name="errorMsg">返回的错误消息</param>
        /// <returns></returns>
        private WorkErrorCode SendTaskOrder(out string errorMsg)
        {
            errorMsg = "";

            if (this.currentTaskInfo == null)
            {
                errorMsg = $"堆垛机[{this.stackingInfo.DeviceIP}]当前任务信息为空，处理异常。";
                LoggerHelper.Error(errorMsg);
                this.currentTaskInfo.ErrorMsg = errorMsg;
                return WorkErrorCode.BizFailed;
            }

            if (!CheckIsConnect(out errorMsg))
            {
                errorMsg = $"堆垛机[{this.stackingInfo.DeviceIP}]连接失败。";
                LoggerHelper.Error(errorMsg);
                this.currentTaskInfo.ErrorMsg = errorMsg;

                this.currentTaskInfo.TaskStatus = StackingTaskStatus.Connecting;
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.ConnectFailed;
                return WorkErrorCode.ConnectFailed;
            }

            // 写取料坐标、放料坐标
            if (!SetCoordinate(this.currentTaskInfo.SpaceMaterialInfo))
            {
                this.currentTaskInfo.TaskStatus = StackingTaskStatus.SendingOrder;
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.BizFailed;
                LoggerHelper.Error($"堆垛机[{this.stackingInfo.DeviceIP}]当前任务信息的取放料信息为空。");
                return WorkErrorCode.BizFailed;
            }

            // 写指令
            int rwResult = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_ORDER, 1,
                new short[] { RWContent.RW_VALUE_1 });
            if (rwResult == RWErrorCode.FAILED)
            {
                errorMsg = $"堆垛机[{this.stackingInfo.DeviceIP}]下发执行任务指令失败";
                LoggerHelper.Error(errorMsg);
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                this.currentTaskInfo.ErrorMsg = errorMsg;
                return WorkErrorCode.WriteFailed;
            }

            // 任务状态
            this.currentTaskInfo.TaskStatus = StackingTaskStatus.SentOrder;
            this.notifyObj = new StackingNotify()
            {
                CurrentTaskNo = this.currentTaskInfo.TaskNo,
                CurrentTaskStatus = this.currentTaskInfo.TaskStatus,
                ErrorCode = this.currentTaskInfo.TaskResultCode,
                SpaceMaterials = this.currentTaskInfo.SpaceMaterialInfo,
                NotifyType = StackingNotifyType.RunTask
            };

            this.notifyObserver();

            this.currentTaskInfo.TaskResultCode = WorkErrorCode.Success;  // 启动任务成功，任务成功后，会有通知消息
            return WorkErrorCode.Success;
        }

        #region 已删除

        /*
    /// <summary>
    /// 等待任务完成异步处理
    /// </summary>
    /// <param name="expiredSeconds">超时时长 单位：s</param>
    /// <returns>是否正常完成任务</returns>
    public WorkErrorCode RunTask()
    {
        return WorkErrorCode.Success;


        string errorMsg = "";
        TimeSpan runTime = TimeSpan.Zero;

        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        do
        {


            this.RecvTaskOrderProcess();                         // 读取收到指令位和指令位
            if (IsOccurFault)
            {
                // continue;
                errorMsg = "放料地址故障！";
                LoggerHelper.Error(errorMsg);
                return WorkErrorCode.BizFailed;
            }

            currentTaskResult = WorkErrorCode.UnKnown;            // 初始化
            break;
        } while (IsOccurFault);

        // 等待执行结果
        while (true)
        {
            Thread.Sleep(WAIT_TIME_INTEVAL);

            // 读任务完成标记
            short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_TASK_FINISHED_FLAG, 1, out errorMsg);
            if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
            {
                // 任务状态
                CurrentTaskStatus = StackingTaskStatus.RecvOrder;
                break;
            }
        }

        // --------------------------------------

        while (true)
        {
            Thread.Sleep(WAIT_TIME_INTEVAL);

            // 读任务完成标记
            short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_TASK_FINISHED_FLAG, 1, out errorMsg);
            if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_0)
            {
                // 堆垛机收到完成任务标记置 0
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1,
                    new short[] { RWContent.RW_VALUE_0 }, out errorMsg))
                {
                    LoggerHelper.Error($"堆垛机完成标记置零失败！{errorMsg}");
                    currentTaskResult = WorkErrorCode.WriteFailed;
                    return currentTaskResult;
                }

                // 任务状态
                CurrentTaskStatus = StackingTaskStatus.Finished;
                break;
            }
        }

        sw.Stop();
        runTime = sw.Elapsed;
        errorMsg = $"堆垛机{ this.stackingInfo.DeviceIP} 任务完成，耗时：{ runTime.TotalSeconds}";
        LoggerHelper.Debug(errorMsg);

        // 通知观察者
        this.notifyObj.ErrorCode = this.currentTaskResult;
        this.notifyObj.ErrorMessage = errorMsg;
        this.notifyObj.CurrentTaskStatus = this.CurrentTaskStatus;
        this.notifyObserver();

        currentTaskResult = WorkErrorCode.Success;
        return currentTaskResult;
        
    }
    

        /// <summary>
        /// 完成入库物料写入任务
        /// </summary>
        /// <param name="expiredSeconds"></param>
        /// <param name="inputCoord">入库物料起始和终止位置坐标</param>
        public async void DoWriteScanInfoTask(short[] inputCoord)
        {
            var task = WriteScanInfoTask(inputCoord);
            await task;
        }

        /// <summary>
        /// 发送入库信息异步方法
        /// </summary>
        /// <param name="expiredSeconds">超时时长</param>
        /// <param name="inputCoord">入库信息【（起点/终点），（面/列/层）】</param>
        /// <returns>任务对象</returns>
        public async Task<WorkErrorCode> WriteScanInfoTask(short[] inputCoord)
        {
            if (inputCoord == null || inputCoord.Length != 6)
            {
                LoggerHelper.Error("参数错误");
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.BizFailed;
                return WorkErrorCode.BizFailed;
            }

            // 异步任务
            var task = Task<bool>.Run(() =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                TimeSpan runTime = TimeSpan.Zero;
                // 开启计时
                sw.Start();

                // 写入库信息 1300
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 6,
                    inputCoord, out string errorMsg))
                {
                    LoggerHelper.Error($"入库信息写入失败！{errorMsg}");
                    this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                    return WorkErrorCode.WriteFailed;
                }

                // 扫码完成标志置1   1002
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(
                    ProtocolAddress.W_SCAN_FINISHED, 1,
                    new short[] { RWContent.RW_VALUE_1 }, out errorMsg))
                {
                    LoggerHelper.Error($"堆垛机写入扫码完成标志失败！{errorMsg}");
                    this.currentTaskInfo.TaskResultCode = WorkErrorCode.WriteFailed;
                    return WorkErrorCode.WriteFailed;
                }

                while (true)
                {
                    Thread.Sleep(WAIT_TIME_INTEVAL);

                    // 读Stacking收到扫码完成标志  1504
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECV_SCAN_FINISHED_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
                    {
                        break;
                    }
                }

                sw.Stop();
                runTime = sw.Elapsed;
                LoggerHelper.Info($"完成一次入库物料信息写入，用时{runTime.TotalMilliseconds}ms。");
                this.currentTaskInfo.TaskResultCode = WorkErrorCode.Success;
                return WorkErrorCode.Success;
            });

            var result = await task;
            return result;
        }

        */

        #endregion

        /// <summary>
        /// 已收到指令处理
        /// </summary>
        /// <returns>处理结果</returns>
        public WorkErrorCode RecvTaskOrderProcess()
        {
            return WorkErrorCode.Success;
        }

        #region 已删除

        /*
        /// <summary>
        /// 等待接收到任务完成异步方法
        /// </summary>
        /// <param name="expiredSeconds">超时时长 单位：s</param>
        /// <returns>是否正常完成任务</returns>
        public async Task<bool> CheckRecvTaskOrderFinished(int expiredSeconds)
        {
            string errorMsg = "";
            TimeSpan runTime = TimeSpan.Zero;
            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error(errorMsg);
                return false;
            }

            bool isFinished = false;

            // 异步任务
            var task = Task<bool>.Run(() =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                sw.Start();

                while (!isFinished)
                {
                    runTime = sw.Elapsed;
                    // 判断是否超时
                    if (runTime.TotalSeconds > expiredSeconds)
                    {
                        sw.Stop();
                        LoggerHelper.Error("任务已超时！");
                        break;
                    }

                    Thread.Sleep(WAIT_TIME_INTEVAL / 10);

                    // 等待 Stacking 设置“接收到任务标记”为 1
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == StackingRWContent.RW_VALUE_1)
                    {
                        LoggerHelper.Debug($"读取接收到指令位正确：{errorMsg}");
                    }
                    else
                    {
                        continue;
                    }

                    // 指令下发位，PLC 置零，读取该位
                    readResult = ReadStackingInt16Data(ProtocolAddress.W_ORDER, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == StackingRWContent.RW_VALUE_0)
                    {
                        LoggerHelper.Debug($"读取指令位置零正确：{errorMsg}");
                    }
                    else
                    {
                        continue;
                    }

                    // 设置“接收到任务标记”为 0
                    int writeResult = WriteStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1,
                        new short[] { StackingRWContent.RW_VALUE_0 }, out errorMsg);
                    if (writeResult != StackingErrorCode.SUCCESS)
                    {
                        LoggerHelper.Info($"写读取接收到指令位错误：{errorMsg}");
                        return false;
                    }

                    // 判断放料位置是否故障
                    readResult = ReadStackingInt16Data(ProtocolAddress.R_TO_PLACE_MALFUNCTION, 8, out errorMsg);
                    if (readResult != null && readResult.Length > 0)
                    {
                        LoggerHelper.Info($"读取放料位置是否故障正确：{errorMsg}");
                        foreach (short resultValue in readResult)
                        {
                            if (resultValue == StackingRWContent.OCCUR_MALFUNCTION_2)                   // 故障
                            {
                                IsOccurFault = true;
                                break;
                            }
                        }
                    }
                    else
                    {
                        continue;
                    }

                    sw.Stop();
                    runTime = sw.Elapsed;
                    LoggerHelper.Info($"任务已完成！耗时：{runTime.TotalMilliseconds}ms");
                    isFinished = true;

                    if (IsOccurFault)
                    {
                        return false;                         // 返回错误
                    }
                }

                sw.Stop();
                runTime = sw.Elapsed;

                errorMsg = $"任务已超时！耗时：{runTime.TotalMilliseconds}ms";
                LoggerHelper.Info(errorMsg);
                return false;
            });

            var result = await task;
            return result;
        }
        */

        #endregion

        /// <summary>
        /// 写坐标
        /// </summary>
        /// <param name="coordType">坐标类型 0：取料坐标 1：放料坐标</param>
        /// <param name="msg">返回的错误消息</param>
        /// <returns>是否正常完成</returns>
        public WorkErrorCode WriteCoordinate(CoordinateType coordType, out string msg)
        {
            if (!CheckIsConnect(out msg))
            {
                return WorkErrorCode.ConnectFailed;
            }

            int index = 0;
            short[] dataArray = new short[COORDINATE_ROW_COUNT * COORDINATE_COLUMN_COUNT];

            try
            {
                if (coordType == CoordinateType.From)
                {
                    for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
                    {
                        for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                        {
                            dataArray[index++] = (short)fromCoordinate[i, j];
                        }
                    }

                    // 写取料地址
                    int result = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_FROM_COORDINATE,
                        dataArray.Length, dataArray);
                    if (result == 0)
                    {
                        msg = "取料地址写入Stacking失败";
                        return WorkErrorCode.WriteFailed;
                    }
                }
                else
                {
                    for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
                    {
                        for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                        {
                            dataArray[index++] = (short)toCoordinate[i, j];
                        }
                    }

                    // 写放料地址
                    int result = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_TO_COORDINATE,
                        dataArray.Length, dataArray);
                    if (result == 0)
                    {
                        msg = "放料地址写入Stacking失败";
                        return WorkErrorCode.WriteFailed;
                    }
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal($"写Stacking地址操作，设置地址数据异常，{ex.Message}");
                return WorkErrorCode.RWException;
            }

            return WorkErrorCode.Success;
        }

        #endregion

        #region 读写 Stacking 基本方法

        /// <summary>
        /// 读取 Stacking 信息
        /// </summary>
        /// <param name="address">读取地址</param>
        /// <param name="length">读取内容长度</param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public short[] ReadStackingInt16Data(int address, int length, out string errorMsg)
        {
            short[] result = null;
            errorMsg = "";

            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error($"读Stacking入库缓存，Stacking 连接异常");
                return null;
            }

            result = HC_AM600_ModbusTcp.ReadInt16(address, length);
            return result;
        }

        /// <summary>
        /// 写Stacking Int16
        /// </summary>
        /// <param name="index"></param>
        /// <param name="writeLenth"></param>
        /// <param name="pValue"></param>
        /// <returns></returns>
        public int WriteStackingInt16Data(int index, int writeLenth, short[] pValue, out string errorMsg)
        {
            errorMsg = "";

            // 判断参数
            if (index <= 0 || writeLenth <= 0 || pValue == null || pValue.Length == 0 ||
                pValue.Length < writeLenth)
            {
                errorMsg = "参数错误";
                LoggerHelper.Error(errorMsg);
                return 0;
            }

            // 判断 Stacking 是否已连接
            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error($"读Stacking入库缓存，Stacking 连接异常");
                return 0;
            }

            return HC_AM600_ModbusTcp.WriteInt16(index, writeLenth, pValue);
        }


        #endregion

        #region 基本状态

        /// <summary>
        /// 是否为自动模式状态
        /// </summary>
        /// <returns></returns>
        public bool IsAutoRunMode
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RUN_STATUS, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0] == RWContent.RW_VALUE_1;
                }
                else
                {
                    LoggerHelper.Debug($"读取是否自动运行模式错误：{errorMsg}");
                    return false;
                }
            }
        }

        /// <summary>
        /// 是否为非忙碌状态
        /// </summary>
        /// <returns></returns>
        public bool IsFree
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_BUSY_STATUS, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0] == RWContent.RW_VALUE_0;
                }
                else
                {
                    LoggerHelper.Debug($"读取是否忙碌状态错误：{errorMsg}");
                    return false;
                }
            }
        }

        /// <summary>
        /// 是否PLC请求扫码（W_PLC请求扫码）
        /// </summary>
        /// <returns></returns>
        public bool IsScanMaterial
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_Stacking_REQUEST_SCAN_BARCODE, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
                {
                    return true;
                }

                LoggerHelper.Error($"读取Stacking{this.stackingInfo.DeviceIP}请求扫码位错误：{errorMsg}");
                return false;
            }
        }

        /// <summary>
        /// 获得实际入库缓存的物料数量
        /// </summary>
        /// <returns></returns>
        public int InputFactCount
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_INPUT_BUFFER_COUNT, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0];
                }
                else
                {
                    LoggerHelper.Debug($"读取入库缓存数量失败：{errorMsg}");
                    return 0;
                }
            }
        }

        #endregion

        /// <summary>
        /// 设置取/放料地址数组
        /// </summary>
        /// <param name="bizType">业务类型 可根据此字段自动添加 入库缓存 或出库缓存地址</param>
        /// <param name="srcCoordinate">源数据</param>
        /// <param name="coordType">坐标类型，0：取料地址 1：放料地址</param>
        /// <param name="errorMsg">错误消息</param>
        /// <returns></returns>
        public bool SetCoordinate(BizType bizType, CoordinateType coordType,
            ushort[,] srcCoordinate, out string errorMsg)
        {
            errorMsg = "";

            try
            {
                if (!(bizType == BizType.InputStorage && coordType == CoordinateType.From) &&
                    !(bizType == BizType.OutputStorage && coordType == CoordinateType.To) &&
                    (srcCoordinate == null ||
                    srcCoordinate.Length != COORDINATE_COLUMN_COUNT * COORDINATE_ROW_COUNT))
                {
                    errorMsg = $"设置取/放料地址参数错误！";
                    LoggerHelper.Fatal(errorMsg);
                    return false;
                }

                // 入库缓存
                if (bizType == BizType.InputStorage && coordType == CoordinateType.From)
                {
                    this.fromCoordinate = InputBufferCoord;
                    return true;
                }

                // 出库缓存
                if (bizType == BizType.OutputStorage && coordType == CoordinateType.To)
                {
                    this.toCoordinate = OutputBufferCoord;
                    return true;
                }

                for (int i = 0; i < MaterialCountPeerPackage; i++)          // 只使用有效数据 
                {
                    if (srcCoordinate[i, 0] == 0 /*|| srcCoordinate[i, 0] > storageRowCount*/)    // 排验证
                    {
                        errorMsg = $"设置取料地址参数排坐标错误：{srcCoordinate[i, 0]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }
                    if (srcCoordinate[i, 1] == 0 /*|| srcCoordinate[i, 1] > storeColumnCount*/)   // 列验证
                    {
                        errorMsg = $"设置取料地址参数列坐标错误：{srcCoordinate[i, 1]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }
                    if (srcCoordinate[i, 2] <= 0 || srcCoordinate[i, 2] > storageLayerCount)     // 层验证
                    {
                        errorMsg = $"设置取料地址参数层坐标错误：{srcCoordinate[i, 2]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }

                    for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                    {
                        if (coordType == 0)
                        {
                            this.fromCoordinate[i, j] = srcCoordinate[i, j];
                        }
                        else
                        {
                            this.toCoordinate[i, j] = srcCoordinate[i, j];
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                errorMsg = $"设置取料地址异常！{ex.Message}";
                LoggerHelper.Fatal(errorMsg);
                return false;
            }

            return true;
        }

        #region 工具方法

        /// <summary>
        /// 判断坐标是否为同一列上连续的库位
        /// </summary>
        /// <param name="currentCoordinate"></param>
        /// <returns></returns>
        private bool CheckCoordIsContinuous(ushort[,] currentCoordinate)
        {
            if (currentCoordinate == null || currentCoordinate.Length == 0 ||
                currentCoordinate.Length % 3 != 0)
            {
                return false;
            }

            ushort rowIndex = currentCoordinate[0, 0];
            ushort columnIndex = currentCoordinate[0, 1];
            ushort levelIndex = currentCoordinate[0, 2];
            for (int i = 0; i < currentCoordinate.Length / 3; i++)
            {
                // 排相等
                if (currentCoordinate[i, 0] != rowIndex)
                {
                    return false;
                }

                // 列相等
                if (currentCoordinate[i, 1] != columnIndex)
                {
                    return false;
                }

                // 层连续
                if (currentCoordinate[i, 2] != levelIndex++)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 设置坐标
        /// </summary>
        /// <param name="spacesStore">起止库位和物料信息</param>
        /// <param name="isSetStackingCoord">是否设置Stacking的坐标值</param>
        /// <returns>是否正常执行</returns>
        public bool SetCoordinate(List<ModifiedSpaceMaterial> spacesStore, bool isSetStackingCoord = true)
        {
            if (spacesStore == null || spacesStore.Count == 0)
            {
                LoggerHelper.Error("参数错误");
                return false;
            }

            List<WarehouseSpace> spaces = new List<WarehouseSpace>();
            foreach (var item in spacesStore)
            {
                spaces = spaces.Append(item.SrcSpace).ToList();
            }

            // 2、控制 Stacking 控制出库操作
            this.fromCoordinate = GetCoordinateBySpace(spaces);
            if (fromCoordinate == null || fromCoordinate.Length == 0)
            {
                LoggerHelper.Error($"库区[{ this.warehouseArea.Id}]获取坐标数组失败！");
                return false;
            }

            spaces = new List<WarehouseSpace>();
            foreach (var item in spacesStore)
            {
                spaces.Add(item.DestSpace);
            }

            this.toCoordinate = GetCoordinateBySpace(spaces);
            if (toCoordinate == null || toCoordinate.Length == 0)
            {
                LoggerHelper.Error($"库区[{ this.warehouseArea.Id}]获取坐标数组失败！");
                return false;
            }

            if (isSetStackingCoord && (!this.SetCoordinate(BizType.ShiftingStorage, CoordinateType.From,
                fromCoordinate, out string errorMsg) || !this.SetCoordinate(BizType.ShiftingStorage,
                CoordinateType.To, toCoordinate, out errorMsg)))
            {
                LoggerHelper.Error($"库区[{ this.warehouseArea.Id}]写入坐标数组失败！{errorMsg}");
                return false;
            }

            return true;
        }

        /// <summary>
        /// 获取库位列表的坐标
        /// </summary>
        /// <param name="spaces"></param>
        /// <returns></returns>
        public static ushort[,] GetCoordinateBySpace(List<WarehouseSpace> spaces)
        {
            if (spaces == null || spaces.Count == 0 || spaces.Count > StackingController.COORDINATE_ROW_COUNT)
            {
                return null;
            }

            ushort[,] coordinateRes = new ushort[StackingController.COORDINATE_ROW_COUNT,
                        StackingController.COORDINATE_COLUMN_COUNT];
            int idx = 0;
            foreach (var item in spaces)
            {
                coordinateRes[idx, 0] = (ushort)(item.RowIndex.HasValue ? item.RowIndex.Value : 0);
                coordinateRes[idx, 1] = (ushort)(item.ColumnIndex.HasValue ? item.ColumnIndex.Value : 0);
                coordinateRes[idx, 2] = (ushort)(item.LayerIndex.HasValue ? item.LayerIndex.Value : 0);

                idx++;
            }

            return coordinateRes;
        }

        #endregion

        #region 通知观察者

        /// <summary>
        /// 通知观察者处理
        /// </summary>
        public override void notifyObserver()
        {
            if (observers == null || observers.Count == 0)
            {
                LoggerHelper.Info("库区[{ this.warehouseArea.Id}]无观察者");
                return;
            }

            foreach (var obs in observers)
            {
                obs.response(ObserverNotifyType.StackingProcess, this.notifyObj);
            }
        }

        /// <summary>
        /// 通知观察者告警
        /// </summary>
        public override void notifyWarningObserver()
        {
            if (observers == null || observers.Count == 0)
            {
                LoggerHelper.Info("库区[{ this.warehouseArea.Id}]无观察者");
                return;
            }

            foreach (var obs in observers)
            {
                obs.warningResponse(ObserverWarningNotifyType.StackingControllerWarning, this.notifyObj);   // 待修改
            }
        }

        #endregion

        /// <summary>
        /// 显示系统消息
        /// </summary>
        /// <param name="message"></param>
        private void ShowWndMsg(string message)
        {
            if(this.ShowWndMessage != null)
            {
                this.ShowWndMessage(message);
            }
        }
    }
}
